package pers.jing.android.helper.work

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.random.Random

/**
 * 任务管理器
 */
class WorkManager {

    val mScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    val mutex = Mutex()

    val works = mutableMapOf<String, Work>()
    val jobs = mutableMapOf<String, Job>()

    suspend fun <T: Work> execute(work: T, onProgress: (Int) -> Unit,
                                  onError: (Exception) -> Unit) = mutex.withLock {
        jobs[work.id]?.cancelAndJoin()
        jobs[work.id] = mScope.launch {
             try {
                onProgress(1)
                when(work) {
                    is PeriodWork -> executePeriodWork(work, onProgress)
                    else -> work.run()
                }
                onProgress(100)
            } catch (e: Exception) {
                onError(e)
            } finally {
                mutex.withLock {
                    jobs.remove(work.id)
                    works.remove(work.id)
                }
            }
        }
        works[work.id] = work
    }

    /**
     * 执行周期任务
     */
    private suspend fun executePeriodWork(periodWork: PeriodWork, onProgress: (Int) -> Unit) {
        val startTime = System.currentTimeMillis()
        val durationMillSeconds = periodWork.duration.inWholeMilliseconds
        while(System.currentTimeMillis() - startTime <= durationMillSeconds) {
            periodWork.run()
            delay(periodWork.interval.inWholeMilliseconds)
            val progress = (System.currentTimeMillis() - startTime) * 100 / durationMillSeconds
            onProgress(progress.toInt().coerceAtMost(100))
        }
    }

    suspend fun stopTask(id: String) = mutex.withLock {
        jobs[id]?.cancelAndJoin()
        jobs.remove(id)
        works.remove(id)
    }
}